package org.example.hrpc.common.spi.loader;

import org.apache.commons.lang3.StringUtils;
import org.example.hrpc.common.spi.annodation.SPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 加载器
 * 首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例，
 * 然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。
 * 这其中，getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader，若缓存未命中，则创建一个新的实例
 * exmaple:getExtensionLoader(HelloService.class).getExtension("HelloWorldServiceImpl");
 *
 * LOADERS根据Robot.class获得ExtensionLoader
 * 根据扩展名optimusPrime，从cachedInstaces缓存中获取对应扩展实现类实例
 * 如果缓存未命中，则createExtension会创建扩展实现类实例：
 *      先从cachedClasses集合中获取 扩展名 对应的 扩展实现类对象（这里缓存未命中则会去文件中加载）
 *      再根据扩展实现类对象 从 cachedObjects集合获取 扩展实现类实例（未命中则创建）
 * 同时会将创建的扩展实现类实例 加入 cachedInstance 集合映射中
 **/
public class ExtensionLoader<T> {

    private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);

    private static final String DIRECTORY = "META-INF/hrpc/";

    // 扩展接口与ExtensionLoader映射关系
    private static final Map<Class<?>, ExtensionLoader<?>> LOADERS = new ConcurrentHashMap<>();

    // 该ExtensionLoader加载的扩展名与扩展实现对象之间的映射关系
    private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    // 该Extension加载的扩展名与扩展实现类之间的映射关系
    // 但是还不明白这里为什么使用Holder保存Map对象？
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    // 扩展实现类到扩展实现类实例的映射
    private final Map<Class<?>, Object> cachedObjects = new ConcurrentHashMap<>();

    // 当前ExtensionLoader实例负责加载的扩展接口
    private final Class<T> clazz;

    public ExtensionLoader(Class<T> clazz) {
        this.clazz = clazz;
    }

    /**
     * 根据扩展接口获取该接口对应的ExtensionLoader，如果缓存未命中则新建
     * 根据对应的class对象获取指定的ExtensionLoader
     *
     * @param clazz 类对象
     * @param <T>   泛型
     * @return {@link ExtensionLoader}
     */
    public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz) {
        if (Objects.isNull(clazz)) {
            throw new NullPointerException("extension clazz is null");
        }
        if (!clazz.isInterface()) {
            throw new IllegalArgumentException("extension clazz (" + clazz + ") is not interface!");
        }
        if (!clazz.isAnnotationPresent(SPI.class)) {
            throw new IllegalArgumentException("extension clazz (" + clazz + ") without @" + SPI.class + " Annotation");
        }
        ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) LOADERS.get(clazz);
        if (Objects.nonNull(extensionLoader)) {
            return extensionLoader;
        }
        LOADERS.putIfAbsent(clazz, new ExtensionLoader<>(clazz));
        return (ExtensionLoader<T>) LOADERS.get(clazz);
    }


    /**
     * 获取name对应的实现类
     *
     * @param name 扩展名
     * @return 类对象
     */
    public T getExtension(String name) {
        SPI annotation = clazz.getAnnotation(SPI.class);
        name = StringUtils.isBlank(name) ? annotation.value() : name;
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        Holder<Object> objectHolder = cachedInstances.get(name);
        if (objectHolder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            objectHolder = cachedInstances.get(name);
        }
        Object value = objectHolder.getValue();
        if (value == null) {
            synchronized (cachedInstances) {
                if (value == null) {
                    value = createExtension(name);
                    objectHolder.setValue(value);
                }
            }
        }
        return (T) value;
    }

    /**
     * 获取所有names对应的实现类数组
     *
     * @param names 扩展名集合
     * @return 类对象
     */
    public Map<String, T> getExtension(List<String> names) {
        Map<String, T> result = new HashMap<>();
        names.forEach(name -> {
            if (StringUtils.isBlank(name)){
                return;
            }
            Holder<Object> objectHolder = cachedInstances.get(name);
            if (objectHolder == null) {
                cachedInstances.putIfAbsent(name, new Holder<>());
                objectHolder = cachedInstances.get(name);
            }
            Object value = objectHolder.getValue();
            if (value == null) {
                synchronized (cachedInstances) {
                    if (value == null) {
                        value = createExtension(name);
                        objectHolder.setValue(value);
                    }
                }
            }
            result.put(name,(T)value);
        });

        return result;
    }

    /**
     * 创建对象缓存
     *
     * @param name 扩展名
     * @return object对象
     */
    private T createExtension(final String name) {
        // 从配置文件中加载所有的扩展类，可得到 “配置项名称”到“配置类”的映射关系
        Class<?> aClass = getExtensionClasses().get(name);
        if (aClass == null) {
            throw new IllegalArgumentException("name is error");
        }
        Object o = cachedObjects.get(aClass);
        if (o == null) {
            try {
                // 目前代码应该有问题，newInstance方法，如果该扩展类未提供空构造器就会出问题
                cachedObjects.putIfAbsent(aClass, aClass.newInstance());
                o = cachedObjects.get(aClass);
            } catch (InstantiationException | IllegalAccessException e) {
                throw new IllegalStateException("Extension instance(name: " + name + ", class: "
                        + aClass + ")  could not be instantiated: " + e.getMessage(), e);

            }
        }
        return (T) o;
    }

    /**
     * 从配置文件中加载所有的扩展类，可得到“配置项名称”到“配置类”的映射关系表
     * 我们在通过名称获取拓展类之前，首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表（Map<名称, 拓展类>），
     * 之后再根据拓展项名称从映射关系表中取出相应的拓展类即可
     *
     * 这里如果不明白 请参考 dubbo2.7 官网 文档
     * https://dubbo.apache.org/zh/docsv2.7/dev/source/dubbo-spi/#2spi-%E7%A4%BA%E4%BE%8B
     * 解释的非常详细
     * 假设Robot.class扩展点，通过getExtensionLoader(robot.class)获取ExtensionLoader后，
     * 再通过extensionLoader.getExtension("optimusPrime")获取Extenion
     * 其中 配置文件可能是这么写的：配置文件名为org.example.hrpc.example.Robot
     * 内容为：
     *     optimusPrime = org.exmaple.hrpc.example.OptimusPrime
     *     bumblebee = org.example.hrpc.example.Bumblebee
     *
     * 该函数目前的功能：就是找到所有实现Robot服务的类，建立扩展实现类名（optimusPrime）到扩展实现类之间的映射
     * @return {@link Map}
     */
    public Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.getValue();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.getValue();
                if (classes == null) {
                    classes = loadDirectory(new HashMap<>(16));
                    cachedClasses.setValue(classes);
                }
            }
        }
        return classes;
    }

    /**
     * 加载配置类
     *
     * @param classes map集合
     * @return
     */
    private Map<String, Class<?>> loadDirectory(final Map<String, Class<?>> classes) {
        String fileName = DIRECTORY + clazz.getName();
        try {
            ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fileName)
                    : ClassLoader.getSystemResources(fileName);
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    loadResources(classes, url);
                }
            }
        } catch (IOException t) {
            logger.error("load extension class error {}", fileName, t);
        }
        return classes;
    }

    /**
     * 根据类型加载资源文件
     * 这里取巧了，Dubbo中实现的方法更加通用，
     * @param classes map集合
     * @param url     {@link URL}
     * @throws IOException
     */
    private void loadResources(final Map<String, Class<?>> classes, final URL url) throws IOException {
        try (InputStream inputStream = url.openStream()) {
            Properties properties = new Properties();
            properties.load(inputStream);
            properties.forEach((key, value) -> {
                String name = (String) key;
                String classPath = (String) value;
                if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) {
                    try {
                        loadClass(classes, name, classPath);
                    } catch (ClassNotFoundException e) {
                        throw new IllegalStateException("load extension resources error", e);
                    }
                }
            });

        } catch (IOException e) {
            throw new IllegalStateException("load extension resources error", e);
        }
    }

    /**
     * 加载类对象
     *
     * @param classes   map集合
     * @param name      扩展名
     * @param classPath 类路径
     * @throws ClassNotFoundException
     */
    private void loadClass(final Map<String, Class<?>> classes,
                           final String name, final String classPath) throws ClassNotFoundException {
        Class<?> subClass = Class.forName(classPath);
        if (!clazz.isAssignableFrom(subClass)) {
            throw new IllegalStateException("load extension resources error," + subClass + " subtype is not of " + clazz);
        }
        Class<?> oldClass = classes.get(name);
        if (oldClass == null) {
            classes.put(name, subClass);
        } else if (oldClass != subClass) {
            throw new IllegalStateException("load extension resources error,Duplicate class " + clazz.getName() + " name " + name + " on " + oldClass.getName() + " or " + subClass.getName());
        }
    }

    /**
     * The type Holder.
     *
     * @param <T> the type parameter.
     */
    public static class Holder<T> {

        private volatile T value;

        /**
         * Gets value.
         *
         * @return the value
         */
        public T getValue() {
            return value;
        }

        /**
         * Sets value.
         *
         * @param value the value
         */
        public void setValue(final T value) {
            this.value = value;
        }
    }
}
